package com.link2room.rodimus.service.impl;

import com.link2room.common.constant.DefaultValue;
import com.link2room.common.dao.ICommonDao;
import com.link2room.common.entity.BaseEntity;
import com.link2room.common.exception.SysException;
import com.link2room.common.jpa.criteria.CriteriaUtil;
import com.link2room.common.jpa.result.QueryResult;
import com.link2room.common.service.ICommonService;
import com.link2room.common.util.ClassConverUtil;
import com.link2room.common.util.DateUtil;
import com.link2room.common.util.L2RAssert;
import com.link2room.rodimus.constant.DrawConstant;
import com.link2room.rodimus.dto.result.base.CouponConsumeTempDto;
import com.link2room.rodimus.dto.result.base.DrawPrizeBaseDto;
import com.link2room.rodimus.entity.DrawActivityEntity;
import com.link2room.rodimus.entity.DrawCodeEntity;
import com.link2room.rodimus.entity.DrawJackpotEntity;
import com.link2room.rodimus.entity.DrawPrizeBaseEntity;

import com.link2room.rodimus.service.IDrawService;
import com.link2room.rodimus.util.LotteryUtil;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.redis.util.RedisLockRegistry;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.stream.Collectors;

import static com.link2room.enums.exception.RodimusSysExceptionEnum.COUPON_ERROR;
import static com.link2room.enums.exception.RodimusSysExceptionEnum.DRAW_ERROR;


/**
 * Created by LN on 2017/7/11.21:21
 */
@Component("drawService")
public class DrawServiceImpl implements IDrawService {

    @Autowired
    private RedisLockRegistry doDrawLock;
    @Resource
    private ICommonDao commonDao;
    @Autowired
    private RedisLockRegistry drawNoLock;
    @Resource
    private ICommonService commonService;

    private final static String DRAWNO = "DRAWNO";

    @Override
    @Transactional
    public DrawPrizeBaseDto doDraw(String activityCode) throws Exception {
        Lock lock = doDrawLock.obtain(activityCode);
        if (lock.tryLock(2, TimeUnit.SECONDS)) {
            try {
                DrawJackpotEntity countFind = new DrawJackpotEntity();
                countFind.setIsOccupied(DefaultValue.F);
                countFind.setIsHalt(DefaultValue.F);
                countFind.setActivityCode(activityCode);
                long count = commonDao.getCount(DrawJackpotEntity.class, CriteriaUtil.getCriteriaAllEQ(countFind), null);
                L2RAssert.isTrue(count == 0, new SysException(DRAW_ERROR, "奖池空了."));
                Random random = new Random();
                int c = random.nextInt((int) count + 1);
                DrawJackpotEntity prizeFind = new DrawJackpotEntity();
                prizeFind.setIsOccupied(DefaultValue.F);
                prizeFind.setIsHalt(DefaultValue.F);
                prizeFind.setActivityCode(activityCode);
                QueryResult<DrawJackpotEntity> prizeRes = commonDao.findAll(DrawJackpotEntity.class, CriteriaUtil.getCriteriaAllEQ(prizeFind), c + 1, 1, null, null);
                if (prizeRes != null && CollectionUtils.isNotEmpty(prizeRes.getResultlist())) {
                    DrawJackpotEntity drawJackpot = prizeRes.getResultlist().get(0);
                    String prizeCode = drawJackpot.getPrizeCode();
                    DrawPrizeBaseEntity drawPrizeBase = selectDrawPrizeBase(activityCode, prizeCode);
                    L2RAssert.isTrue(drawPrizeBase.getNum() <= drawPrizeBase.getOccupied(), new SysException(DRAW_ERROR, "[prizeCode]" + prizeCode + "奖品全部领走"));
                    drawJackpot.setIsOccupied(DefaultValue.T);
                    drawPrizeBase.setOccupied(drawPrizeBase.getOccupied() + 1);
                    commonDao.merge(drawJackpot);
                    commonDao.merge(drawPrizeBase);
                    DrawPrizeBaseDto resDto = new DrawPrizeBaseDto();
                    ClassConverUtil.copyProperties(drawPrizeBase, resDto);
                    resDto.setPrizeNo(drawJackpot.getPrizeNo());
                    resDto.setValue(drawJackpot.getValue());
                    return resDto;
                } else {
                    throw new SysException(DRAW_ERROR, "[activityCode]" + activityCode + " 抽奖异常,在奖池里没有找到奖项");
                }
            } finally {
                lock.unlock();
            }
        } else {
            throw new SysException(DRAW_ERROR, "[抽奖人数太多,请稍后重试!]");
        }
    }

    @Override
    public String generateDrawCouponNo() throws Exception {
        Lock lock = drawNoLock.obtain(DRAWNO);
        if (lock.tryLock(2, TimeUnit.SECONDS)) {
            try {
                List params = new ArrayList<>();
                params.add(DRAWNO);
                String no = commonService.findOneFieldByProcedure("create_No", params);
                return no;
            } finally {
                lock.unlock();
            }
        } else {
            throw new SysException(COUPON_ERROR, "生成" + DRAWNO + "异常,稍后重试");
        }
    }

    @Override
    @Transactional
    public DrawPrizeBaseDto doDraw(String lhotelGroupCode, String activityCode, String drawCode,CouponConsumeTempDto couponConsumRes) throws Exception {
        Lock lock = doDrawLock.obtain(activityCode);
        if (lock.tryLock(2, TimeUnit.SECONDS)) {
            try {
                DrawActivityEntity drawActivityEntity = checkActivityDateAllowed(lhotelGroupCode, activityCode);
                DrawCodeEntity drawCodeEentity = null;
                if (DefaultValue.T.equals(drawActivityEntity.getNeedDrawCode())) {
                    drawCodeEentity = checkCodeHasUsed(lhotelGroupCode, activityCode, drawCode);
                }
                List<DrawPrizeBaseEntity> drawPrizeBaseList = selectDrawPrizeBaseEntity(lhotelGroupCode, activityCode);
                L2RAssert.isTrue(CollectionUtils.isEmpty(drawPrizeBaseList), new SysException(DRAW_ERROR, "活动没有可用奖项[activityCode]." + activityCode));
                List<LotteryUtil.DrawRate> drawRateList = drawPrizeBaseList.stream()
                        .filter(drawPrizeBaseEntity -> {
                            if (DrawConstant.NumStrategy.Limited.equals(drawPrizeBaseEntity.getNumStrategy())) {//若 奖池策略限制数量 做数量扣减
                                Integer currentNum = drawPrizeBaseEntity.getCurrentNum();
                                if (currentNum <= 0) {
                                    return false;
                                }
                            }
                            if (drawPrizeBaseEntity.getRate() <= 0) {
                                return false;
                            }
                            return true;
                        })
                        .map(entity -> {
                            String code = entity.getCode();
                            Integer rate = entity.getRate();
                            return new LotteryUtil.DrawRate(code, Double.valueOf(rate));
                        })
                        .collect(Collectors.toList());
                String winningCode = new LotteryUtil(drawRateList).randomColunmCode();
                DrawPrizeBaseEntity winningPrize = drawPrizeBaseList.stream()
                        .filter(drawPrizeBaseEntity -> winningCode.equals(drawPrizeBaseEntity.getCode()))
                        .findFirst().orElseThrow(() -> new SysException(DRAW_ERROR, "抽奖异常，请重试"));
                Integer occupied = winningPrize.getOccupied();
                winningPrize.setOccupied(occupied + 1);
                if (DrawConstant.NumStrategy.Limited.equals(winningPrize.getNumStrategy())) {
                    Integer currentNum = winningPrize.getCurrentNum();
                    winningPrize.setCurrentNum(currentNum - 1);
                }
                if (drawCodeEentity != null) {
                    commonDao.merge(drawCodeEentity);
                }
                commonDao.merge(winningPrize);

                //couponConsumRes do
                if(couponConsumRes != null){
                    commonDao.save(couponConsumRes.getBaseEntity());
                    commonDao.saveList(couponConsumRes.getDetails());
                    commonDao.mergeList(couponConsumRes.getUpdateCouponDetails());
                }
                DrawPrizeBaseDto resDto = new DrawPrizeBaseDto();
                ClassConverUtil.copyProperties(winningPrize, resDto);
                return resDto;
            } finally {
                lock.unlock();
            }
        } else {
            throw new SysException(DRAW_ERROR, "[抽奖人数太多,请稍后重试!]");
        }
    }

    @Override
    @Transactional
    public void updatePrizeBases(List<DrawPrizeBaseEntity> deletes, List<DrawPrizeBaseEntity> updateEntities) throws Exception {
        commonDao.saveOrMergeList(updateEntities);
        if(CollectionUtils.isNotEmpty(deletes)){
            commonDao.delete(DrawPrizeBaseEntity.class,deletes.stream().map(BaseEntity::getId).toArray());
        }
    }

    public List<DrawPrizeBaseEntity> selectDrawPrizeBaseEntity(String lhotelGroupCode, String activityCode) throws Exception {
        DrawPrizeBaseEntity find = new DrawPrizeBaseEntity();
        find.setActivityCode(activityCode);
        find.setLhotelGroupCode(lhotelGroupCode);
        find.setIsHalt(DefaultValue.F);
        QueryResult<DrawPrizeBaseEntity> qres = commonDao.findAll(DrawPrizeBaseEntity.class, CriteriaUtil.getCriteriaAllEQ(find), null, null, null, null);
        return qres.getResultlist();
    }

    /**
     * 查找、检查有效时间并返回抽奖活动entity
     *
     * @param lhotelGroupCode
     * @param activityCode
     * @return
     * @throws Exception
     */
    private DrawActivityEntity checkActivityDateAllowed(String lhotelGroupCode, String activityCode) throws Exception {
        DrawActivityEntity find = new DrawActivityEntity();
        find.setCode(activityCode);
        find.setLhotelGroupCode(lhotelGroupCode);
        DrawActivityEntity drawEntity = commonDao.findOne(DrawActivityEntity.class, CriteriaUtil.getCriteriaAllEQ(find));
        L2RAssert.isTrue(drawEntity == null, new SysException(DRAW_ERROR, "活动不存在."));
        L2RAssert.isTrue(DefaultValue.T.equals(drawEntity.getIsHalt()), new SysException(DRAW_ERROR, "活动已停用."));
        Date startDate = drawEntity.getStartDate();
        L2RAssert.isTrue(startDate == null, new SysException(DRAW_ERROR, "startDate is null.检查配置."));
        Date endDate = drawEntity.getEndDate();
        L2RAssert.isTrue(endDate == null, new SysException(DRAW_ERROR, "endDate is null.检查配置."));
        Date now = new Date();
        boolean isInDate = now.after(startDate) && now.before(endDate);
        L2RAssert.isTrue(
                !isInDate,
                new SysException(DRAW_ERROR, "不在活动日期。活动日期:[" + DateUtil.format(startDate, "yyyy-MM-dd HH:mm:ss") + "到]"
                        + DateUtil.format(endDate, "yyyy-MM-dd HH:mm:ss"))
        );

        LocalTime startTime = drawEntity.getActiveStartTime().toLocalTime();
        LocalTime endTime = drawEntity.getActiveEndTime().toLocalTime();
        if (startTime != null && endTime != null) {
            boolean isInTime;
            isInTime = LotteryUtil.isInInterval(startTime, endTime, LocalTime.now());
            L2RAssert.isTrue(
                    !isInTime,
                    new SysException(DRAW_ERROR, "不在活动时间。时间:[" + startTime + "到]"
                            + endTime)
            );
        }
        return drawEntity;
    }

    /**
     * 抽奖码检查
     *
     * @param activityCode
     * @param drawCode
     * @return
     * @throws Exception
     */
    private DrawCodeEntity checkCodeHasUsed(String lhotelGroupCode, String activityCode, String drawCode) throws Exception {
        DrawCodeEntity find = new DrawCodeEntity();
        find.setCode(drawCode);
        find.setLhotelGroupCode(lhotelGroupCode);
        find.setActivityCode(activityCode);
        DrawCodeEntity drawCodeEntity = commonDao.findOne(DrawCodeEntity.class, CriteriaUtil.getCriteriaAllEQ(find));
        L2RAssert.isTrue(drawCodeEntity == null, new SysException(DRAW_ERROR, "抽奖码不合法."));
        L2RAssert.isTrue(DefaultValue.T.equals(drawCodeEntity.getIsUsed()), new SysException(DRAW_ERROR, "抽奖码已经使用过."));
        return drawCodeEntity;
    }

    /**
     * 使用抽奖码
     *
     * @param drawCodeEntity
     * @throws Exception
     */
    void useDrawCode(DrawCodeEntity drawCodeEntity) throws Exception {
        drawCodeEntity.setIsUsed(DefaultValue.T);
        commonDao.merge(drawCodeEntity);
    }

    /*@Override
    @Transactional
    public void jackPotUpdate(String lhotelGroupCode, String activityCode, String prizeCode, String operation, Integer num) throws Exception {
        Lock lock = doDrawLock.obtain(activityCode);
        if (lock.tryLock(2, TimeUnit.SECONDS)) {
            try {
                boolean isOpen = DrawConstant.JackPotOperation.OPEN.equals(operation);
                DrawJackpotEntity updateFind = new DrawJackpotEntity();
                updateFind.setActivityCode(activityCode);
                updateFind.setPrizeCode(prizeCode);
                updateFind.setLhotelGroupCode(lhotelGroupCode);
                updateFind.setIsHalt(isOpen ? DefaultValue.T : DefaultValue.F);
                updateFind.setIsOccupied(DefaultValue.F);
                long c = commonDao.getCount(DrawJackpotEntity.class, CriteriaUtil.getCriteriaAllEQ(updateFind), null);
                L2RAssert.isTrue(num > c,
                        new SysException(DRAW_ERROR,
                                "修改数量超出现有数量:" + " [activityCode] " + activityCode +
                                        "\n[prizeCode] " + prizeCode +
                                        "\n[currentNum]" + c + "[isHalt] " + updateFind.getIsHalt() +
                                        " \n[num] " + num));

                QueryResult<DrawJackpotEntity> qres = commonDao.findAll(DrawJackpotEntity.class, CriteriaUtil.getCriteriaAllEQ(updateFind), 1, num, null, null);

                if (qres != null && CollectionUtils.isNotEmpty(qres.getResultlist())) {
                    String isHalt = isOpen ? DefaultValue.F : DefaultValue.T;
                    qres.getResultlist().stream()
                            .forEach(entity -> entity.setIsHalt(isHalt));
                    commonDao.mergeList(qres.getResultlist());
                }
            } finally {
                lock.unlock();
            }
        } else {
            throw new SysException(DRAW_ERROR, "修改失败，[抽奖人数太多,请稍后重试!]");
        }
    }*/

    @Override
    @Transactional
    public void jackPotUpdate(String lhotelGroupCode, String activityCode, String prizeCode, Integer availableCount) throws Exception {
        Lock lock = doDrawLock.obtain(activityCode);
        if (lock.tryLock(2, TimeUnit.SECONDS)) {
            try {
                Long currentAvailabCount = selectAvailableJackpotCount(lhotelGroupCode, activityCode, prizeCode);
                L2RAssert.isTrue(availableCount > currentAvailabCount, new SysException(DRAW_ERROR, "可用数量不够,当前可用数量为:" + currentAvailabCount));
                Long currentGroundingJackpotCount = selectCurrentGroundingJackpotCount(lhotelGroupCode, activityCode, prizeCode);
                //要开放 > 已开放
                boolean willOpen = availableCount > currentGroundingJackpotCount;
                long changeNum = Math.abs(availableCount - currentGroundingJackpotCount);
                if (changeNum == 0) {
                    return;
                }
                DrawJackpotEntity updateFind = new DrawJackpotEntity();
                updateFind.setActivityCode(activityCode);
                updateFind.setPrizeCode(prizeCode);
                updateFind.setLhotelGroupCode(lhotelGroupCode);
                updateFind.setIsHalt(willOpen ? DefaultValue.T : DefaultValue.F);
                updateFind.setIsOccupied(DefaultValue.F);
                QueryResult<DrawJackpotEntity> qres = commonDao.findAll(DrawJackpotEntity.class, CriteriaUtil.getCriteriaAllEQ(updateFind), 1, (int) changeNum, null, null);
                if (qres != null && CollectionUtils.isNotEmpty(qres.getResultlist())) {

                    String isHalt = willOpen ? DefaultValue.F : DefaultValue.T;
                    Set<Long> ids = qres.getResultlist().stream()
                            .map(BaseEntity::getId)
                            .collect(Collectors.toSet());
                    if (CollectionUtils.isEmpty(ids)) {
                        return;
                    }
                    commonDao.excuteByNative(updateJackpotIsHaltById(isHalt, ids), Collections.EMPTY_LIST);
//                    commonDao.updateList(qres.getResultlist());
                }
            } finally {
                lock.unlock();
            }
        } else {
            throw new SysException(DRAW_ERROR, "修改失败，[抽奖人数太多,请稍后重试!]");
        }
    }

    private String updateJackpotIsHaltById(String isHalt, Set<Long> ids) {
        return "UPDATE `draw_jackpot` SET is_halt = '" + isHalt + "' WHERE id IN (" + StringUtils.join(ids.toArray(), ",") + ")";
    }

    /**
     * 查询已上架可抽奖奖品数量
     *
     * @return
     */
    private Long selectCurrentGroundingJackpotCount(String lhotelGroupCode, String activityCode, String prizeCode) throws Exception {
        DrawJackpotEntity count = new DrawJackpotEntity();
        count.setActivityCode(activityCode);
        count.setPrizeCode(prizeCode);
        count.setLhotelGroupCode(lhotelGroupCode);
        count.setIsOccupied(DefaultValue.F);
        count.setIsHalt(DefaultValue.F);
        return commonDao.getCount(DrawJackpotEntity.class, CriteriaUtil.getCriteriaAllEQ(count), null);
    }


    /**
     * 查询可用奖池奖品可用数量
     *
     * @param lhotelGroupCode
     * @param activityCode
     * @param prizeCode
     * @return
     * @throws Exception
     */
    private Long selectAvailableJackpotCount(String lhotelGroupCode, String activityCode, String prizeCode) throws Exception {
        DrawJackpotEntity count = new DrawJackpotEntity();
        count.setActivityCode(activityCode);
        count.setPrizeCode(prizeCode);
        count.setLhotelGroupCode(lhotelGroupCode);
        count.setIsOccupied(DefaultValue.F);
        return commonDao.getCount(DrawJackpotEntity.class, CriteriaUtil.getCriteriaAllEQ(count), null);
    }

    private DrawPrizeBaseEntity selectDrawPrizeBase(String activityCode, String prizeCode) throws Exception {
        DrawPrizeBaseEntity find = new DrawPrizeBaseEntity();
        find.setCode(prizeCode);
        find.setActivityCode(activityCode);
        find.setIsHalt(DefaultValue.F);
        DrawPrizeBaseEntity entity = commonDao.findOne(DrawPrizeBaseEntity.class, CriteriaUtil.getCriteriaAllEQ(find));
        L2RAssert.isTrue(entity == null, new SysException(DRAW_ERROR, "[prizeCode]" + prizeCode + "没有对象奖品"));
        return entity;
    }

	@Override
	@Transactional
	public DrawPrizeBaseDto doDrawWithPrizeCode(String lhotelGroupCode, String activityCode, String prizeCode, CouponConsumeTempDto couponConsumRes) throws Exception {
		Lock lock = doDrawLock.obtain(activityCode);
        if (lock.tryLock(2, TimeUnit.SECONDS)) {
            try {
                checkActivityDateAllowed(lhotelGroupCode, activityCode);
                List<DrawPrizeBaseEntity> drawPrizeBaseList = selectDrawPrizeBaseEntity(lhotelGroupCode, activityCode);
                L2RAssert.isTrue(CollectionUtils.isEmpty(drawPrizeBaseList), new SysException(DRAW_ERROR, "活动没有可用奖项[activityCode]." + activityCode));
                DrawPrizeBaseEntity winningPrize = drawPrizeBaseList.stream()
                        .filter(drawPrizeBaseEntity -> prizeCode.equals(drawPrizeBaseEntity.getCode()))
                        .findFirst().orElseThrow(() -> new SysException(DRAW_ERROR, "抽奖异常，请重试"));
                Integer occupied = winningPrize.getOccupied();
                winningPrize.setOccupied(occupied + 1);
                if (DrawConstant.NumStrategy.Limited.equals(winningPrize.getNumStrategy())) {
                    Integer currentNum = winningPrize.getCurrentNum();
                    winningPrize.setCurrentNum(currentNum - 1);
                }
                commonDao.merge(winningPrize);

                //couponConsumRes do
                if(couponConsumRes != null){
                    commonDao.save(couponConsumRes.getBaseEntity());
                    commonDao.saveList(couponConsumRes.getDetails());
                    commonDao.mergeList(couponConsumRes.getUpdateCouponDetails());
                }
                DrawPrizeBaseDto resDto = new DrawPrizeBaseDto();
                ClassConverUtil.copyProperties(winningPrize, resDto);
                return resDto;
            } finally {
                lock.unlock();
            }
        } else {
            throw new SysException(DRAW_ERROR, "[抽奖人数太多,请稍后重试!]");
        }
	}
}
